package uploader

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"server/business/config"
	"server/business/errors/common_errors"
	file2 "server/business/file_server2/file"
	"server/business/utils"
	"server/framework/web/context"
	errors2 "server/framework/web/errors"
	"strconv"
)

type FragmentUploader struct {
	fileTmpPath    string
	fileTargetPath string
}

func (f *FragmentUploader) getRetrySlices(dir string, sliceNum int) ([]int, error) {
	files, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	if len(files) == 0 {
		return []int{-1}, nil
	}
	uploadMap := make(map[int]string, len(files))
	for _, file := range files {
		if file.IsDir() || file.Name() == "meta.json" {
			continue
		}

		var num int
		num, err = strconv.Atoi(file.Name())
		if err != nil {
			// 解析出错了就跳过，等待client再上传一次
			fmt.Println("FragmentUploader：解析已上传分片错误， err: ", err.Error())
			continue
		}
		uploadMap[num] = ""
	}
	if len(uploadMap) == 0 {
		return []int{-1}, nil
	}

	retrySlices := make([]int, 0, sliceNum-len(uploadMap))
	for i := 0; i < sliceNum; i++ {
		if _, ok := uploadMap[i]; !ok {
			retrySlices = append(retrySlices, i)
		}
	}

	return retrySlices, nil
}

func (f *FragmentUploader) HandlePrepare(c *context.Context) {
	//	服务端检测对应文件meta信息是否存在，
	//		若存在，则比对meta，若meta比对一致，表明文件上传过但未完成，则返回需要重传的切片序号
	//		若meta不存在或者meta比对不一致，则保存meta信息，创建文件夹用于保存分片信息

	bs, err := io.ReadAll(c.R.Body)
	defer c.R.Body.Close()
	if err != nil {
		c.RespBadRequest(errors2.BadRequestError.Error())
		return
	}
	fileMeta := &file2.FileMeta{}
	err = json.Unmarshal(bs, fileMeta)
	if err != nil {
		c.RespBadRequest(errors2.BadRequestError.Error())
		return
	}

	fid := fileMeta.Fid
	dir := filepath.Join(f.fileTmpPath, fid)
	metaPath := filepath.Join(f.fileTmpPath, fid, "meta.json")
	if file2.NeedCreateFileMeta(metaPath, fileMeta) {
		fmt.Println("fileMeta文件不存在或已失效，需要重新创建；客户端需要上传完整文件")
		err = os.RemoveAll(dir)
		if err != nil {
			fmt.Println(err)
		}
		err = file2.SetFileMeta(metaPath, fileMeta)
		if err != nil {
			log.Println(err.Error())
			c.RespSystemErr(common_errors.UploadError.Error())
			return
		}
		// 通知客户端需要传送整个文件
		c.RespOk([]int{-1})
	} else {
		var retrySlices []int
		retrySlices, err = f.getRetrySlices(dir, fileMeta.SliceNum)
		if err != nil {
			log.Println(err.Error())
			c.RespSystemErr(common_errors.UploadError.Error())
			return
		}
		c.RespOk(retrySlices)
		fmt.Println("存在fileMeta文件，需要传输的切片号: ", retrySlices)
	}
}

var mockCnt = 0 // todo: 每次用完需要重置，在handleFinish处重置

func (f *FragmentUploader) HandleUpload(c *context.Context) {
	file, _, err := c.R.FormFile("file")
	defer file.Close()
	if err != nil {
		c.RespBadRequest(common_errors.FieldFileNotFound.Error())
		return
	}

	//mock: 测试失败重传，分片3、5共计需要重传3次才能成功
	//if c.R.FormValue("idx") == "3" || c.R.FormValue("idx") == "5" {
	//	if mockCnt < 3 {
	//		c.RespBadRequest("mock: 测试上传失败进行重传")
	//		mockCnt++
	//		return
	//	}
	//}

	partMd5 := c.R.FormValue("md5")
	hash := md5.New()
	teeReader := io.TeeReader(file, hash)

	idx := c.R.FormValue("idx")
	var fl *os.File
	path := filepath.Join(f.fileTmpPath, c.R.FormValue("fid"), idx)
	fl, err = utils.CreateFile(path)
	defer fl.Close()
	if err != nil {
		log.Println(err.Error())
		c.RespSystemErr(common_errors.UploadError.Error())
		return
	}

	_, err = io.Copy(fl, teeReader)
	if err != nil {
		log.Println(err.Error())
		c.RespSystemErr(common_errors.UploadError.Error())
		return
	}
	md5Str := hex.EncodeToString(hash.Sum(nil))
	if md5Str != partMd5 {
		c.RespSystemErr(common_errors.Md5NotMatch.Error())
		return
	}

	c.RespOk(fmt.Sprintf("上传分片 %s 成功", idx))
}

// HandleFinished 文件上传完成，检查文件完整性、合并文件
func (f *FragmentUploader) HandleFinished(c *context.Context) {
	//mockCnt = 0
	//c.RespOk("上传成功")
	//return
	bs, err := io.ReadAll(c.R.Body)
	defer c.R.Body.Close()
	if err != nil {
		c.RespBadRequest(errors2.BadRequestError.Error())
		return
	}
	fileMeta := &file2.FileMeta{}
	err = json.Unmarshal(bs, fileMeta)
	if err != nil {
		c.RespBadRequest(errors2.BadRequestError.Error())
		return
	}

	ext := filepath.Ext(fileMeta.Name)
	dir := filepath.Join(f.fileTmpPath, fileMeta.Fid)

	var files []os.DirEntry
	files, err = os.ReadDir(dir)
	if err != nil {
		log.Println(err.Error())
		c.RespSystemErr(common_errors.UploadError.Error())
		return
	}

	if len(files)-1 != fileMeta.SliceNum {
		log.Println(fmt.Sprintf("切片数量不对，实际切片数：%d，期望切片数：%d\n", len(files), fileMeta.SliceNum))
		c.RespSystemErr(common_errors.UploadError.Error())
		return
	}

	targetPath := filepath.Join(f.fileTargetPath, fileMeta.Fid+ext)
	var targetFile *os.File
	targetFile, err = os.Create(targetPath)
	defer targetFile.Close()
	if err != nil {
		log.Println(err.Error())
		c.RespSystemErr(common_errors.UploadError.Error())
		return
	}
	hash := md5.New()

	for i := 0; i < fileMeta.SliceNum; i++ {
		filePath := filepath.Join(f.fileTmpPath, fileMeta.Fid, strconv.Itoa(i))
		var file *os.File
		file, err = os.Open(filePath)
		if err != nil {
			log.Println(err.Error())
			c.RespSystemErr(common_errors.UploadError.Error())
			_ = file.Close()
			return
		}

		teeReader := io.TeeReader(file, hash)
		_, err = io.Copy(targetFile, teeReader)
		if err != nil {
			log.Println(err.Error())
			c.RespSystemErr(common_errors.UploadError.Error())
			_ = file.Close()
			return
		}
		_ = file.Close()
	}

	fileMd5 := hex.EncodeToString(hash.Sum(nil))
	if fileMd5 != fileMeta.Md5 {
		c.RespSystemErr(common_errors.Md5NotMatch.Error())
		err = os.Remove(targetPath)
		err = os.RemoveAll(dir)
		if err != nil {
			fmt.Println(err)
		}
		return
	}
	// mock: 演示时，注释掉os.RemoveAll(dir)；分片暂不删除tmp数据
	err = os.RemoveAll(dir)
	if err != nil {
		fmt.Println(err)
	}
	config.FileStorage[fileMeta.Fid+ext] = fileMeta.Name
	c.RespOk("上传成功")
}

func NewFragmentUploader(fileTmpPath string, fileTargetPath string) *FragmentUploader {
	return &FragmentUploader{
		fileTmpPath:    fileTmpPath,
		fileTargetPath: fileTargetPath,
	}
}
